重新再思考一下,外面的struct,裡面是否真的有需要放個struct。
筆者認為,屬於struct內部的變數,放什麼型態都可好處理,除了struct以外,要特別想過自己的佈局,再下決定是不是要擺上struct。
我發覺是這樣的,如果struct內部成員有足以抽象出明顯的『業務』,比起用struct,interface可能會比較合適,而如果只是存放稍微複雜一點的資料,而且真的只是單純作為存放資料用途,可以選擇struct。
如舉例的DB struct來說,Connector是連線,那連線會做很多動作,建立連線呀,連線有問題的處理,連線關閉處理等等,明顯不是個簡簡單單的資料存放單位。
所以是個interface。
那麼我們前一篇舉例的employee(員工)和Tool(工具)呢?Tool算是一種業務嗎?不就只是不同工具?應該算是某種格式的資料儲存?
有兩個問題,筆者認為是要先思考的。
Tool內部的資料對employee很重要嗎?employee需要了解Tool內部的資料嗎?
Tool裡面,為何需要記錄這些資料的,它們的用途在哪裡?
實際上大多時候,外面的往往並不關心裡面的過程,只在乎結果。
這跟現實社會的很多道理是很相像的,你會關心月初能不能拿到薪水,並不在乎發給你薪水的金錢是怎麼流動和處理的。你會關心新上架的遊戲在自己的PS4能不能順利開玩,而不會管這遊戲還需要在PC和XBOX或者Switch一起上架。
工具再怎麼千奇百怪,只要能夠幫你順利完成工作,實際上有些問題你不會管它,譬如說工具價格,它對於老闆可能是個問題,但對員工可沒差別。
所以,Tool在這邊,它的內部資料或規格,對於employee(員工)不是很重要,employee(員工)關心的是工具能不能用,可不可以達到目的,Tool(工具)只要能夠讓employee(員工)可以知道結果即可。
employee(員工)只需要Tool(工具)的method,不需要知道Tool(工具)的struct長怎麼樣。
現在我們就來做個小練習吧,假設工具有其壽命,有使用次數,而且不專屬某位員工特定做使用。就像小學生打掃教室,公用的掃把不會綁定說一定是哪位小朋友專用,小朋友在,掃把在,小朋友畢業了,掃把就不能給其他人使用吧。
因此,第一步,employee裡面不放入Tool的struct,兩個struct沒有耦合關係。
// Tool 工具
type Tool struct {
id string
name string
power float64
weight float64
remainUsedTime int // 剩下可使用次數
}
// Employee 員工
type Employee struct {
id int
age int
}
第二步,工具是人使用的,所以需要把工具遞給員工,但遞出的是哪一把工具不一定。
// UseTool 使用工具
func (e *Employee) UseTool(t *Tool) {
}
第三步,Tool的使用壽命資料是記在Tool的struct裡面,所以這些資訊只有Tool可提供來做檢查。
// UseTool 使用工具
func (e *Employee) UseTool(t *Tool) error {
if t.remainUsedTime < 0 {
return errors.New("此工具使用壽命已盡,無法再使用")
}
return nil
}
第四步,就如同這幾篇討論的,工具類型可能不同,『t.remainUsedTime』這項資訊可能只有在Tool才有,若來個Tool2狀況就不是這樣了。
// Tool2 工具
type Tool2 struct {
id string
name string
power float64
weight float64
UsedTime int // 使用過的次數
}
// UseTool2 使用工具
func (e *Employee) UseTool2(t *Tool2) error {
if t.UsedTime >= 10000 {
return errors.New("此工具使用壽命已盡,無法再使用")
}
return nil
}
第五步,可想而知以上的方法不可行,我們要『工具』自己提供好,是不是可以使用的結果,而不是在『員工』的method裡面幫忙做這個判斷。
可以執行,但不可以幫忙判斷。
// IsCanUse 可否使用
func (t *Tool) IsCanUse() bool {
if t.remainUsedTime < 0 {
return false
}
return true
}
// IsCanUse 可否使用
func (t2 *Tool2) IsCanUse() bool {
if t2.UsedTime >= 10000 {
return false
}
return true
}
// UseTool 使用工具
func (e *Employee) UseTool(t *Tool) error {
// 執行判斷
isOk := t.IsCanUse()
if !isOk {
return errors.New("此工具使用壽命已盡,無法再使用")
}
return nil
}
第六步,如果傳入的工具參數,先指定好struct,則只能侷限在使用某一種工具,於是改成符合用有我們需要的method就能傳入的interface設計。
// ITool 工具的method規格
type ITool interface {
IsCanUse() bool
}
// UseTool 使用工具
func (e *Employee) UseTool(t ITool) error {
isOk := t.IsCanUse()
if !isOk {
return errors.New("此工具使用壽命已盡,無法再使用")
}
return nil
}
以上,我想就是struct內部會選擇interface型態的變數,道理是這樣來的。